using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.Windows.Forms;
using FractalSpline;
using FreeImageAPI;
using Mogre;
using OpenViewer.Framework;
using OpenViewer.Models;
using ThirdLife.Render.MogrePlugin.Common;
using Box=FractalSpline.Box;
using Color=System.Drawing.Color;
using IPlugin=Mogre.IPlugin;
using PixelFormat=System.Drawing.Imaging.PixelFormat;
using Vector3=Math3D.Vector3;

namespace ThirdLife.Render.MogrePlugin
{
  public class UserControlRenderingView : IRenderingView, IPrimManager
  {
    private readonly IntPtr _passedInHandle;
    private readonly int _passedInHeight;
    private readonly int _passedInWidth;

    private readonly Dictionary<Guid, EntityWrapper> m_avatars = new Dictionary<Guid, EntityWrapper>();
    private readonly Dictionary<Guid, EntityWrapper> m_prims = new Dictionary<Guid, EntityWrapper>();

    private readonly List<EntityWrapper> m_updatePrims = new List<EntityWrapper>();
    private IMetaverseModelAdapter _modelAdapter;
    private Camera m_camera;
    private Light m_light;
    private string m_rendersys = "OpenGL Rendering Subsystem";
    protected SceneManager m_sceneMgr;
    private Viewport m_viewport;
    private int mMoveCam;
    private int mPitch;
    private Root mRoot;
    private RenderWindow mWindow;
    private int mYaw;
    private bool _quit;
    Form inputForm;

    public UserControlRenderingView(IntPtr handle, int width, int height)
    {
      _passedInHandle = handle;
      _passedInWidth = width;
      _passedInHeight = height;
    }

    #region IPrimManager Members

    public void RequestPrimUpdate(EntityWrapper wrapper)
    {
      lock (m_updatePrims)
      {
        m_updatePrims.Add(wrapper);
      }
    }

    #endregion

    #region IRenderingView Members

    public void SetAdapter(IMetaverseModelAdapter adapter)
    {
      _modelAdapter = adapter;
    }

    public void Setup()
    {
      CreateRoot();
      SetupRenderSystem();
      DefineResources();
      CreateRenderWindowInControl(_passedInHandle);
      InitResources();
      CreateScene();
    }

    public void StartRendering()
    {
      while (!_quit&&RunOneFrame())
        Application.DoEvents();
      inputForm.Hide();
      inputForm = null;
      OgreForm_Disposed(null, null);
    }

    public void StopRendering()
    {
      _quit = true;
    }

    #endregion

    private void CreateRoot()
    {
      mRoot = new Root(String.Format("plugins-{0}.cfg", Util.GetOS()));
      
      
      inputForm = new Form();
      inputForm.Text = "Input Window";
      inputForm.Width = 100;
      inputForm.Height = 100;
      inputForm.TopMost = true;
      inputForm.Show();
      inputForm.KeyDown += KeyboardDown;
      inputForm.KeyUp += KeyboardUp;
    }

    protected void KeyboardDown(object sender, KeyEventArgs e)
    {
      switch (e.KeyCode)
      {
        case Keys.W:
          mMoveCam |= 1;
          break;
        case Keys.S:
          mMoveCam |= 2;
          break;
        case Keys.A:
          mMoveCam |= 4;
          break;
        case Keys.D:
          mMoveCam |= 8;
          break;
        case Keys.E:
          mPitch |= 1;
          break;
        case Keys.C:
          mPitch |= 2;
          break;
        case Keys.NumPad4:
          mYaw |= 1;
          break;
        case Keys.NumPad6:
          mYaw |= 2;
          break;
      }
    }

    protected void KeyboardUp(object sender, KeyEventArgs e)
    {
      switch (e.KeyCode)
      {
        case Keys.W:
          mMoveCam &= ~1;
          break;
        case Keys.S:
          mMoveCam &= ~2;
          break;
        case Keys.A:
          mMoveCam &= ~4;
          break;
        case Keys.D:
          mMoveCam &= ~8;
          break;
        case Keys.E:
          mPitch &= ~1;
          break;
        case Keys.C:
          mPitch &= ~2;
          break;
        case Keys.NumPad4:
          mYaw &= ~1;
          break;
        case Keys.NumPad6:
          mYaw &= ~2;
          break;
        case Keys.T:
          //m_camera.WriteContentsToFile("snapshot.jpg");
          break;
      }
    }

    private void DefineResources()
    {
      var cf = new ConfigFile();
      cf.Load("resources.cfg", "\t:=", true);

      // Go through all sections & settings in the file
      ConfigFile.SectionIterator section = cf.GetSectionIterator();

      String secName, typeName, archName;

      // Normally we would use the foreach syntax, which enumerates the values, but in this case we need CurrentKey too;
      while (section.MoveNext())
      {
        secName = section.CurrentKey;
        ConfigFile.SettingsMultiMap settings = section.Current;
        foreach (var pair in settings)
        {
          typeName = pair.Key;
          archName = pair.Value;
          ResourceGroupManager.Singleton.AddResourceLocation(archName, typeName, secName);
        }
      }
    }

    private void SetupRenderSystem()
    {
      foreach (RenderSystem rs in mRoot.GetAvailableRenderers())
      {
        if (rs.Name == m_rendersys)
        {
          mRoot.RenderSystem = rs;
          break;
        }
      }

      if (mRoot.RenderSystem == null)
      {
        throw new Exception(String.Format("Render system \"{0}\" not found", m_rendersys));
      }

      mRoot.RenderSystem.SetConfigOption("Full Screen", "No");
      mRoot.RenderSystem.SetConfigOption("Video Mode", "800 x 600@ 32-bit colour");
    }

    private void CreateRenderWindow()
    {
      mWindow = mRoot.Initialise(true, "Main Ogre Window");
    }

    private void CreateRenderWindowInControl(IntPtr handle)
    {
      // Create Render Window
      mRoot.Initialise(false, "Third Life");
      var misc = new NameValuePairList();
      misc["externalWindowHandle"] = handle.ToString();
      mWindow = mRoot.CreateRenderWindow("Third Life", (uint) _passedInWidth, (uint) _passedInHeight, false, misc);
    }

    private void InitResources()
    {
      TextureManager.Singleton.DefaultNumMipmaps = 6;
      ResourceGroupManager.Singleton.InitialiseAllResourceGroups();
    }

    private void CreateScene()
    {
      string name = "Metaverse";
      m_sceneMgr = mRoot.CreateSceneManager(SceneType.ST_EXTERIOR_CLOSE, name);
      m_sceneMgr.AmbientLight = new ColourValue(0.75f, 0.75f, 0.75f);
      // m_sceneMgr.AmbientLight = ColourValue.Black;

      //if you have lighting/shadow problems, try changing the following type
      m_sceneMgr.ShadowTechnique = ShadowTechnique.SHADOWTYPE_TEXTURE_ADDITIVE;

      m_camera = m_sceneMgr.CreateCamera(name + "Camera");
      m_camera.Position = new Mogre.Vector3(128f, 50f, 150f);
      // Look back along -Z
      m_camera.LookAt(new Mogre.Vector3(128f, 10f, -100f));
      m_camera.NearClipDistance = 1;
      //  m_camera.AspectRatio = 1.33f;
      m_camera.AutoAspectRatio = true;
      m_camera.FOVy = new Radian(0.524f);
      //  System.Console.WriteLine("field of view " + m_camera.FOVy.ValueDegrees.ToString());
      //  System.Console.WriteLine("focal length " + m_camera.FocalLength);

      m_viewport = mWindow.AddViewport(m_camera);
      m_viewport.BackgroundColour = new ColourValue(0.0f, 0.0f, 0.0f, 1.0f);

      mRoot.FrameStarted += FrameStarted;

      m_sceneMgr.SetSkyBox(true, "SkyBox");

      CreateTerrainMaterial("grass_1024.jpg", 0.2f, 0, "alpha_dirt.png", "bark-256x256.jpg", 0.1f, "alpha_dirt2.png",
                            "Grass.jpg", 0.1f);

      OgreUtil.CreatePlane(m_sceneMgr, "water", "Ocean2_Cg", 128, 21, 128, 256, 256, 50, 50, 1, 1);

      m_light = OgreUtil.CreateLight(m_sceneMgr, 3, "sceneLight", new Mogre.Vector3(120, 70, 128),
                                     new Mogre.Vector3(0.1f, -1f, -0.1f));
    }

    public void CreateTerrainMaterial(string baseTextureImage, float baseTextureScale, int numberSplats,
                                      string texture1Alpha, string texture1Image, float texture1Scale,
                                      string texture2Alpha, string texture2Image, float texture2Scale)
    {
      OgreUtil.CreateTerrainMaterial("test_splat2", baseTextureImage, baseTextureScale, numberSplats, texture1Alpha,
                                     texture1Image, texture1Scale, texture2Alpha, texture2Image, texture2Scale);
    }

    private bool FrameStarted(FrameEvent e)
    {
      _modelAdapter.ProcessIncomingAvatars(AddNewAvatar);
      _modelAdapter.ProcessIncomingPrim(AddNewPrim);
      ProcessPrimUpdates();
      UpdateCamera(e.timeSinceLastFrame);

      _modelAdapter.ProcessLand(AddTerrain);

      return true;
    }

    protected void UpdateCamera(float t)
    {
      if ((mPitch & 1) > 0)
      {
        PitchCamera(3.0f*1.0f*t);
      }
      if ((mPitch & 2) > 0)
      {
        PitchCamera(3.0f*-1.0f*t);
      }
      if ((mYaw & 1) > 0)
      {
        YawCamera(3.0f*1.0f*t);
      }
      if ((mYaw & 2) > 0)
      {
        YawCamera(3.0f*-1.0f*t);
      }
      if (mMoveCam > 0)
      {
//mMoveCam bits: 1=forward, 2=backward, 4=left, 8=right, 16=up, 32=down
        var vCamMove = new Mogre.Vector3(0, 0, 0);
        float mvscale = 150.0f*t;

        if ((mMoveCam & 1) > 0)
          vCamMove += new Mogre.Vector3(0, 0, -1); //Vector3.NEGATIVE_UNIT_Z;
        if ((mMoveCam & 2) > 0)
          vCamMove += new Mogre.Vector3(0, 0, 1); //Vector3.UNIT_Z;
        if ((mMoveCam & 4) > 0)
          vCamMove += new Mogre.Vector3(-1, 0, 0); //Vector3.NEGATIVE_UNIT_X;
        if ((mMoveCam & 8) > 0)
          vCamMove += new Mogre.Vector3(1, 0, 0); //Vector3.UNIT_X;
        if ((mMoveCam & 16) > 0)
          vCamMove += new Mogre.Vector3(0, 1, 0); //Vector3.UNIT_Y;
        if ((mMoveCam & 32) > 0)
          vCamMove += new Mogre.Vector3(0, -1, 0); //Vector3.NEGATIVE_UNIT_Y;

        vCamMove *= mvscale;
        MoveCamera(vCamMove);
      }
    }

    protected void MoveCamera(Mogre.Vector3 vec)
    {
      var mogreVec = new Mogre.Vector3(vec.x, vec.y, vec.z);
      m_camera.MoveRelative(mogreVec);
    }

    protected void YawCamera(float r)
    {
      m_camera.Yaw(new Radian(r));
    }

    protected void PitchCamera(float r)
    {
      m_camera.Pitch(new Radian(r));
    }

    protected void AddTerrain(Dictionary<int, float[]> map)
    {
      string fileName = CreateHeightMap(map);
      CreateTerrain(256, 256, fileName, "test_splat2");
    }

    protected virtual void CreateTerrain(float x, float z, string heightmap, string material)
    {
    }

    protected string CreateHeightMap(Dictionary<int, float[]> landMaps)
    {
      // Console.WriteLine("creating new map image");
      var ourMap = new Bitmap(256, 256, PixelFormat.Format24bppRgb);

      string fileName = "myterrain1.jpg";
      string path = Util.MakePath("media", "materials", "textures", fileName);

      for (int patchy = 0; patchy < 16; patchy++)
      {
        for (int patchx = 0; patchx < 16; patchx++)
        {
          float[] currentPatch = landMaps[patchy*16 + patchx];

          for (int cy = 0; cy < 16; cy++)
          {
            for (int cx = 0; cx < 16; cx++)
            {
              int x = cx + patchx*16;
              int y = cy + patchy*16;

              var col = (int) (Util.Clamp(currentPatch[cy*16 + cx]/60, 0.0f, 1.0f)*255);

              ourMap.SetPixel(y, x, Color.FromArgb(col, col, col));
            }
          }
        }
      }

      ImageCodecInfo jpegEncoder = OgreUtil.GetImageEncoder("JPEG");

      var parms = new EncoderParameters(1);
      parms.Param[0] = new EncoderParameter(Encoder.ColorDepth, 24L);

      var resize = new Bitmap(ourMap, new Size(257, 257));
      resize.Save(path, jpegEncoder, parms);

      uint image = FreeImage.Load(FREE_IMAGE_FORMAT.FIF_JPEG, path, 0);
      image = FreeImage.ConvertTo8Bits(image);
      FreeImage.Save(FREE_IMAGE_FORMAT.FIF_JPEG, image, path, 0);
      return fileName;
    }

    private void AddNewPrim(Guid primID, EntityObject entity)
    {
      Mogre.Vector3 scale;
      if (entity.PrimShape != null)
      {
        //scale is in LL axis so convert, err no don't as we rotate the node instead
        //  scale = new Vector3( entity.PrimShape.Scale.y, entity.PrimShape.Scale.z, entity.PrimShape.Scale.x);
        scale = new Mogre.Vector3(entity.PrimShape.Scale.y, entity.PrimShape.Scale.x, entity.PrimShape.Scale.z);
      }
      else
      {
        scale = new Mogre.Vector3(0.25f, 0.25f, 0.25f);
      }
      SceneNode node = CreatePrimNode(primID, entity.StartPosition, scale, entity.PrimShape);
      var holder = new EntityWrapper(entity, node, this);
      lock (m_prims)
      {
        m_prims[primID] = holder;
      }
    }

    private SceneNode CreatePrimNode(Guid primID, Vector3 position, Mogre.Vector3 scale, PrimitiveBaseShape shape)
    {
      var pos = new Mogre.Vector3(position.x, position.y, position.z);
      SceneNode newNode = m_sceneMgr.RootSceneNode.CreateChildSceneNode();
      newNode.AttachObject(CreatePrimObject(primID.ToString(), shape));
      newNode.Position = pos;
      newNode.SetScale((scale));
      newNode.Roll(new Radian(1.574f));
      newNode.Pitch(new Radian(1.574f));
      newNode.Yaw(new Radian(1.574f));
      return newNode;
    }

    private void ProcessPrimUpdates()
    {
      if (m_updatePrims.Count > 0)
      {
        List<EntityWrapper> incoming;
        lock (m_updatePrims)
        {
          incoming = new List<EntityWrapper>(m_updatePrims);
          m_updatePrims.Clear();
        }
        foreach (EntityWrapper wrapper in incoming)
        {
          if ((wrapper.Node != null) && (wrapper.NewShape != null))
          {
            MovableObject ent = wrapper.Node.GetAttachedObject(0);
            wrapper.Node.DetachObject(ent);
            m_sceneMgr.DestroyMovableObject(ent);
            wrapper.Node.AttachObject(CreatePrimObject(wrapper.Ent.FullID.ToString(), wrapper.NewShape));
          }
        }
      }
    }

    private ManualObject CreatePrimObject(string avatarName, PrimitiveBaseShape shape)
    {
      //actually no need to be using the primitivewrapper here
      //also with ogredotnet we should be able to create a custom simpleRenderable object
      //and then just edit its buffers rather than keep recreating a new manual object 
      //everytime a prim is edited
      var primWrap = new PrimitiveWrapper();
      primWrap.Name = avatarName;
      primWrap.Renderer = new RenderToOgre();
      primWrap.Shape = new CustomShape();
      primWrap.Shape.SetMaterial("OpenViewer/Wood");
      primWrap.Material = primWrap.Shape.MaterialName;
      primWrap.Renderer.m_shape = primWrap.Shape;

      if (shape.Type == PrimType.Cylinder)
      {
        primWrap.Prim = new Cylinder(primWrap.Renderer);
      }
      else if (shape.Type == PrimType.Tube)
      {
        primWrap.Prim = new Tube(primWrap.Renderer);
      }
      else if (shape.Type == PrimType.Prism)
      {
        primWrap.Prim = new Prism(primWrap.Renderer);
      }
      else if (shape.Type == PrimType.Ring)
      {
        primWrap.Prim = new Ring(primWrap.Renderer);
      }
      else
      {
        primWrap.Prim = new Box(primWrap.Renderer);
      }

      primWrap.Prim.CutStart = (shape.ProfileBegin/250);
      primWrap.Prim.CutEnd = ((50000 - shape.ProfileEnd)/250);
      primWrap.Prim.Hollow = (shape.ProfileHollow/526);
      primWrap.Prim.UpdateTransforms();
      primWrap.Prim.Render();
      primWrap.Shape.Draw();
      return primWrap.Shape.MogreObj;
    }

    private void AddNewAvatar(Guid avatarID, EntityObject entity)
    {
      SceneNode node = CreateAvatarNode(avatarID, entity.StartPosition);
      var holder = new EntityWrapper(entity, node, this);
      lock (m_avatars)
      {
        m_avatars[avatarID] = holder;
      }
    }

    private SceneNode CreateAvatarNode(Guid avatarID, Vector3 position)
    {
      var pos = new Mogre.Vector3(position.x, position.y, position.z);
      SceneNode newNode = m_sceneMgr.RootSceneNode.CreateChildSceneNode();
      newNode.AttachObject(CreateNewAvatar(avatarID.ToString()));
      newNode.Position = pos;
      newNode.Scale(0.0075f, 0.0075f, 0.0075f);
      return newNode;
    }

    private MovableObject CreateNewAvatar(string avatarName)
    {
      return m_sceneMgr.CreateEntity(avatarName, "ninja.mesh");
    }

    public void OgreForm_Resize(object sender, EventArgs e)
    {
      mWindow.WindowMovedOrResized();
    }

    public void OgreForm_Disposed(object sender, EventArgs e)
    {
      //somewhere down the line, we need to figure out how to properly dispose
    }

    public bool RunOneFrame()
    {
      if (mRoot != null && mRoot.RenderOneFrame())
      {
        return true;
      }
      return false;
    }
  }
}
